// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using ILLink.RoslynAnalyzer.DataFlow;
using ILLink.Shared.DataFlow;
using Microsoft.CodeAnalysis;

namespace ILLink.RoslynAnalyzer.TrimAnalysis
{
    internal readonly struct TrimAnalysisPatternStore
    {
        private readonly Dictionary<IOperation, TrimAnalysisAssignmentPattern> AssignmentPatterns;
        private readonly Dictionary<IOperation, TrimAnalysisFieldAccessPattern> FieldAccessPatterns;
        private readonly Dictionary<IOperation, TrimAnalysisBackingFieldAccessPattern> BackingFieldAccessPatterns;
        private readonly Dictionary<IOperation, TrimAnalysisGenericInstantiationPattern> GenericInstantiationPatterns;
        private readonly Dictionary<IOperation, TrimAnalysisMethodCallPattern> MethodCallPatterns;
        private readonly Dictionary<IOperation, TrimAnalysisReflectionAccessPattern> ReflectionAccessPatterns;
        private readonly Dictionary<IOperation, FeatureCheckReturnValuePattern> FeatureCheckReturnValuePatterns;
        private readonly ValueSetLattice<SingleValue> Lattice;
        private readonly FeatureContextLattice FeatureContextLattice;

        public TrimAnalysisPatternStore(
            ValueSetLattice<SingleValue> lattice,
            FeatureContextLattice featureContextLattice)
        {
            AssignmentPatterns = new Dictionary<IOperation, TrimAnalysisAssignmentPattern>();
            FieldAccessPatterns = new Dictionary<IOperation, TrimAnalysisFieldAccessPattern>();
            BackingFieldAccessPatterns = new Dictionary<IOperation, TrimAnalysisBackingFieldAccessPattern>();
            GenericInstantiationPatterns = new Dictionary<IOperation, TrimAnalysisGenericInstantiationPattern>();
            MethodCallPatterns = new Dictionary<IOperation, TrimAnalysisMethodCallPattern>();
            ReflectionAccessPatterns = new Dictionary<IOperation, TrimAnalysisReflectionAccessPattern>();
            FeatureCheckReturnValuePatterns = new Dictionary<IOperation, FeatureCheckReturnValuePattern>();
            Lattice = lattice;
            FeatureContextLattice = featureContextLattice;
        }

        public void Add(TrimAnalysisBackingFieldAccessPattern pattern)
        {
            if (!BackingFieldAccessPatterns.TryGetValue(pattern.Operation, out var existingPattern))
            {
                BackingFieldAccessPatterns.Add(pattern.Operation, pattern);
                return;
            }

            BackingFieldAccessPatterns[pattern.Operation] = pattern.Merge(FeatureContextLattice, existingPattern);
        }

        public void Add(TrimAnalysisAssignmentPattern trimAnalysisPattern)
        {
            // Finally blocks will be analyzed multiple times, once for normal control flow and once
            // for exceptional control flow, and these separate analyses could produce different
            // trim analysis patterns.
            // The current algorithm always does the exceptional analysis last, so the final state for
            // an operation will include all analysis patterns (since the exceptional state is a superset)
            // of the normal control-flow state.
            // We still add patterns to the operation, rather than replacing, to make this resilient to
            // changes in the analysis algorithm.
            if (!AssignmentPatterns.TryGetValue(trimAnalysisPattern.Operation, out var existingPattern))
            {
                AssignmentPatterns.Add(trimAnalysisPattern.Operation, trimAnalysisPattern);
                return;
            }

            AssignmentPatterns[trimAnalysisPattern.Operation] = trimAnalysisPattern.Merge(Lattice, FeatureContextLattice, existingPattern);
        }

        public void Add(TrimAnalysisFieldAccessPattern pattern)
        {
            if (!FieldAccessPatterns.TryGetValue(pattern.Operation, out var existingPattern))
            {
                FieldAccessPatterns.Add(pattern.Operation, pattern);
                return;
            }

            FieldAccessPatterns[pattern.Operation] = pattern.Merge(FeatureContextLattice, existingPattern);
        }

        public void Add(TrimAnalysisGenericInstantiationPattern pattern)
        {
            if (!GenericInstantiationPatterns.TryGetValue(pattern.Operation, out var existingPattern))
            {
                GenericInstantiationPatterns.Add(pattern.Operation, pattern);
                return;
            }

            GenericInstantiationPatterns[pattern.Operation] = pattern.Merge(FeatureContextLattice, existingPattern);
        }

        public void Add(TrimAnalysisMethodCallPattern pattern)
        {
            if (!MethodCallPatterns.TryGetValue(pattern.Operation, out var existingPattern))
            {
                MethodCallPatterns.Add(pattern.Operation, pattern);
                return;
            }

            MethodCallPatterns[pattern.Operation] = pattern.Merge(Lattice, FeatureContextLattice, existingPattern);
        }

        public void Add(TrimAnalysisReflectionAccessPattern pattern)
        {
            if (!ReflectionAccessPatterns.TryGetValue(pattern.Operation, out var existingPattern))
            {
                ReflectionAccessPatterns.Add(pattern.Operation, pattern);
                return;
            }

            ReflectionAccessPatterns[pattern.Operation] = pattern.Merge(FeatureContextLattice, existingPattern);
        }

        public void Add(FeatureCheckReturnValuePattern pattern)
        {
            if (!FeatureCheckReturnValuePatterns.TryGetValue(pattern.Operation, out var existingPattern))
            {
                FeatureCheckReturnValuePatterns.Add(pattern.Operation, pattern);
                return;
            }

            Debug.Assert(existingPattern == pattern, "Return values should be identical");
        }

        public void ReportDiagnostics(DataFlowAnalyzerContext context, Action<Diagnostic> reportDiagnostic)
        {
            foreach (var assignmentPattern in AssignmentPatterns.Values)
                assignmentPattern.ReportDiagnostics(context, reportDiagnostic);

            foreach (var fieldAccessPattern in FieldAccessPatterns.Values)
                fieldAccessPattern.ReportDiagnostics(context, reportDiagnostic);

            foreach (var backingFieldAccessPattern in BackingFieldAccessPatterns.Values)
                backingFieldAccessPattern.ReportDiagnostics(context, reportDiagnostic);

            foreach (var genericInstantiationPattern in GenericInstantiationPatterns.Values)
                genericInstantiationPattern.ReportDiagnostics(context, reportDiagnostic);

            foreach (var methodCallPattern in MethodCallPatterns.Values)
                methodCallPattern.ReportDiagnostics(context, reportDiagnostic);

            foreach (var reflectionAccessPattern in ReflectionAccessPatterns.Values)
                reflectionAccessPattern.ReportDiagnostics(context, reportDiagnostic);

            foreach (var returnValuePattern in FeatureCheckReturnValuePatterns.Values)
                returnValuePattern.ReportDiagnostics(context, reportDiagnostic);
        }
    }
}
